package gui;

import java.io.File;
import java.net.URL;

import library.Library;
import library.LibraryCollectArtists;
import localFiles.LocalCollectArtists;
import localFiles.PathExtractionPattern;

import org.apache.pivot.beans.BXML;
import org.apache.pivot.beans.Bindable;
import org.apache.pivot.collections.ArrayList;
import org.apache.pivot.collections.List;
import org.apache.pivot.collections.Map;
import org.apache.pivot.collections.Sequence;
import org.apache.pivot.util.Resources;
import org.apache.pivot.util.concurrent.Task;
import org.apache.pivot.util.concurrent.TaskExecutionException;
import org.apache.pivot.util.concurrent.TaskListener;
import org.apache.pivot.wtk.Action;
import org.apache.pivot.wtk.Button;
import org.apache.pivot.wtk.ButtonPressListener;
import org.apache.pivot.wtk.Component;
import org.apache.pivot.wtk.ComponentMouseButtonListener;
import org.apache.pivot.wtk.Dialog;
import org.apache.pivot.wtk.FileBrowserSheet;
import org.apache.pivot.wtk.Menu;
import org.apache.pivot.wtk.MenuHandler;
import org.apache.pivot.wtk.Mouse;
import org.apache.pivot.wtk.Prompt;
import org.apache.pivot.wtk.PushButton;
import org.apache.pivot.wtk.Sheet;
import org.apache.pivot.wtk.SheetCloseListener;
import org.apache.pivot.wtk.SuggestionPopup;
import org.apache.pivot.wtk.TaskAdapter;
import org.apache.pivot.wtk.TextArea;
import org.apache.pivot.wtk.TextInput;
import org.apache.pivot.wtk.TextInputContentListener;
import org.apache.pivot.wtk.TreeView;
import org.apache.pivot.wtk.TreeViewSelectionListener;
import org.apache.pivot.wtk.Window;
import org.apache.pivot.wtk.content.ListItem;
import org.apache.pivot.wtk.content.ListViewItemRenderer;
import org.apache.pivot.wtk.content.TreeBranch;
import org.apache.pivot.wtk.content.TreeNode;
import org.apache.pivot.wtk.media.Image;

import baseStructure.Album;
import baseStructure.Artist;
import baseStructure.Base;
import baseStructure.Log;
import baseStructure.Track;

public class MainWindow extends Window implements Bindable {	
	private List<TreeNode> treeData = new ArrayList<TreeNode>();
	
	private MenuHandler menuHandler = new ContextMenuHandler();
	
	@BXML private Dialog editArtist;
	@BXML private TreeView treeLibraryView;
	@BXML private TextArea textInfo;
	@BXML private PushButton importButton;
	@BXML private PushButton b1, b2, b3, b4, b5, b6, b7;
	
	private static Image trackImage;
	
	@Override
	public void initialize(Map<String, Object> namespace, URL location,
			Resources resources) {
		// TODO: Background tasks complains about not being run on the evendispatch thread if this isn't set to null
		// Seems to work now for some reason. Keeping this here if it ever comes back as I can get a stacktrace now
		//Container.setEventDispatchThreadChecker(null);
		
		try {
			trackImage = Image.load(MainWindow.class.getResource("/gui/audio-x-generic.png"));
		} catch (TaskExecutionException e) {
			e.printStackTrace();
		}
				
		// Context menu
		setMenuHandler(menuHandler);
		
		importButton.getButtonPressListeners().add(new ImportButtonListener());
		
		treeLibraryView.getTreeViewSelectionListeners().add(new TreeViewSelectionListener.Adapter() {
			@Override
			public void selectedNodeChanged(TreeView treeView,
					Object previousSelectedNode) {		
				Object selectedNode = treeView.getSelectedNode();
				
				// In the case where the node is deselected
				if (selectedNode == null) return;
				
				Base selection = (Base) ((TreeNode) selectedNode).getUserData();
				
				textInfo.setText(selection.getInfo());
			}
		});
		
		treeLibraryView.getComponentMouseButtonListeners().add(new ComponentMouseButtonListener.Adapter() {			
			@Override
			public boolean mouseClick(Component component, Mouse.Button button, int x, int y, int count) {
				// If we have a double click
				if (count == 2) {
					TreeView tree = ((TreeView) component);
					
					// Make sure we got a TreeBranch as we can't expand a TreeNode!
					if (tree.getSelectedNode() instanceof TreeBranch)
						tree.expandBranch(tree.getSelectedPath());
				}
				
				return false;
			}
		});
		
		testing();
	}
	
	/**
	 * Clears the tree data list and builds a new one
	 * Then sets the tree data for the TreeView
	 */
	private void createTree() {
		treeData.clear();
		
		for (Artist artist : Library.getInstance().artists) {
			TreeBranch artistBranch = new TreeBranch(artist.getName());
			
			artistBranch.setUserData(artist);
			
			for (Album album : artist.getAlbums()) {
				TreeBranch albumBranch = new TreeBranch(album.getName());
				
				albumBranch.setUserData(album);
				artistBranch.add(albumBranch);
				
				for (Track track : album.getTracks()) {
					TreeNode trackNode = new TreeNode(track.getName());
					trackNode.setUserData(track);
					
					albumBranch.add(trackNode);
				}
			}
			
			treeData.add(artistBranch);
		}
		
		treeLibraryView.setTreeData(treeData);
	}
	
	/**
	 * Oh god the horror
	 */
	private void testing() {
		b1.getButtonPressListeners().add(new ButtonPressListener() {
			public void buttonPressed(Button button) {
				Testing.test1();
			}
		});
		b2.getButtonPressListeners().add(new ButtonPressListener() {
			public void buttonPressed(Button button) {
				Testing.test2();
			}
		});
		b3.getButtonPressListeners().add(new ButtonPressListener() {
			public void buttonPressed(Button button) {
				Testing.test3();
			}
		});
		b4.getButtonPressListeners().add(new ButtonPressListener() {
			public void buttonPressed(Button button) {
				Testing.test4();
			}
		});
		b5.getButtonPressListeners().add(new ButtonPressListener() {
			public void buttonPressed(Button button) {
				Testing.test5();
			}
		});
		b6.getButtonPressListeners().add(new ButtonPressListener() {
			@Override
			public void buttonPressed(Button button) {
				Dialog dialog = new Dialog();
				dialog.setTitle("Search");
				
				TextInput searchInput = new TextInput();
				
				searchInput.getTextInputContentListeners().add(new SearchInputContentListener());
				
				dialog.setContent(searchInput);
				dialog.open(getWindow());
			}
		});
		b7.getButtonPressListeners().add(new ButtonPressListener() {
			public void buttonPressed(Button button) {
				editArtist.open(getWindow());
			}
		});
	}
	
	private class ContextMenuHandler extends MenuHandler.Adapter {
        @Override
        public boolean configureContextMenu(Component component, Menu menu, int x, int y) {
            final Component descendant = getDescendantAt(x, y);
 
            Menu.Section menuSection = new Menu.Section();
            menu.getSections().add(menuSection);
 
            Menu.Item exampleMenuItem = new Menu.Item("Right clicked on TreeView");
            exampleMenuItem.setAction(new Action() {
                @Override
                public void perform(Component source) {
                    String message = descendant.toString();
 
                    Prompt.prompt(message, MainWindow.this);
                }
            });
 
            /* You should be able to create custom context menus based on what you clicked at
             * Here I add a context menu for right-clicking on a TreeView
             */
            if (descendant instanceof TreeView)
            	menuSection.add(exampleMenuItem);
 
            return false;
        }
    }
	
	private class ImportButtonListener implements ButtonPressListener {
        @Override
        public void buttonPressed(Button button) {
        	final FileBrowserSheet fileBrowserSheet = new FileBrowserSheet();
        	
        	fileBrowserSheet.setMode(FileBrowserSheet.Mode.SAVE_TO);
        	fileBrowserSheet.open(MainWindow.this, new SheetCloseListener() {
				@Override
				public void sheetClosed(Sheet sheet) {
					if (sheet.getResult()) {
                        Sequence<File> selectedFiles = fileBrowserSheet.getSelectedFiles();
                        
                        ImportFolderTask importFolderTask = new ImportFolderTask(selectedFiles.get(0));
                        
                        TaskListener<Void> taskListener = new TaskListener<Void>() {
							@Override
							public void executeFailed(Task<Void> task) {
								// Just so we can get a real stacktrace if something bad happens, temporary
								try {
									throw task.getFault().getCause();
								} catch (Throwable e) {
									e.printStackTrace();
								}
							}

							@Override
							public void taskExecuted(Task<Void> task) {
								Log.i("ImportFolderTask executed");
								
								createTree();
								
								Log.i(treeData.getLength());
							}
						};
						
						importFolderTask.execute(new TaskAdapter<Void>(taskListener));
                    }
				}
			});
        }
    }
	
	private static class ImportFolderTask extends Task<Void> {
		private File directory;
		
		public ImportFolderTask(File directory) {
			this.directory = directory;
		}
		
		@Override
		public Void execute() throws TaskExecutionException {
			PathExtractionPattern pattern = new PathExtractionPattern("" +
					"%artistname%/" +
						"%albumyear% - %albumname%:" +
						"[%albumyear%] %albumname%:" +
						"(%albumyear%) %albumname%:" +
						"%albumname% (%albumyear%):" +
						"%albumname% [%albumyear%]:" +
						"%albumname%/" +
							"%tracknum%. %trackname%.%extension%:" +
							"%artistname - %tracknum% - %trackname%.%extension%");
			
	    	long startTime = System.nanoTime();
	    	LibraryCollectArtists i = new LocalCollectArtists(Library.getInstance(), directory, pattern);
	    	
			while (!i.getStatus()) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) { }
			}
			
			Log.i(String.format("Time used: %.2fs", (System.nanoTime() - startTime)/1000000000.0));

			return null;
		}
	}
	
	private static class SearchInputContentListener extends TextInputContentListener.Adapter {
		private SuggestionPopup suggestionPopup = new SuggestionPopup();
		
		public SearchInputContentListener() {
			((ListViewItemRenderer) suggestionPopup.getSuggestionRenderer()).setShowIcon(true);
		}
		
		@Override
		public void textChanged(final TextInput textInput) {
			GenerateSuggestionsTask generateSuggestionsTask = new GenerateSuggestionsTask(textInput.getText());
            
            TaskListener<ArrayList<ListItem>> taskListener = new TaskListener<ArrayList<ListItem>>() {
				@Override
				public void executeFailed(Task<ArrayList<ListItem>> task) {
					// Just so we can get a real stacktrace if something bad happens, temporary
					try {
						throw task.getFault().getCause();
					} catch (Throwable e) {
						e.printStackTrace();
					}
				}

				@Override
				public void taskExecuted(Task<ArrayList<ListItem>> task) {
					ArrayList<ListItem> suggestions = task.getResult();
					
					/* We don't want more than 4 suggestions
					 * List can be longer just make sure there aren't thousands of items
					 * in the list as SuggestionPopup will have to render the whole list
					 * freezing the gui in the prosess
					 */
					if (suggestions.getLength() > 4)
						suggestions = (ArrayList<ListItem>) suggestions.remove(0, 4);
					
					if (suggestions.getLength() > 0) {
	                    suggestionPopup.setSuggestionData(suggestions);
	                    suggestionPopup.open(textInput);
	                }
				}
            };
            
            generateSuggestionsTask.execute(new TaskAdapter<ArrayList<ListItem>>(taskListener));
		}
		
		@Override
        public void textRemoved(TextInput textInput, int index, int count) {
            suggestionPopup.close();
        }
	}
	
	private static class GenerateSuggestionsTask extends Task<ArrayList<ListItem>> {
		private String text;
		
		public GenerateSuggestionsTask(String text) {
			this.text = text;
		}
		
		@Override
		public ArrayList<ListItem> execute() throws TaskExecutionException {
			ArrayList<ListItem> suggestions = new ArrayList<ListItem>();
			
			for (Artist artist : Library.getInstance().artists) {
				if (artist.getName().toLowerCase().contains(text.toLowerCase())) {
					suggestions.add(new ListItem(null, "Artist: " + artist.getName()));
				}
				
				for (Album album : artist.getAlbums()) {
					if (album.getName().toLowerCase().contains(text.toLowerCase())) {
						suggestions.add(new ListItem(null, "Album: " + album.getName()));
					}
					
					for (Track track : album.getTracks()) {
						if (track.getName().toLowerCase().contains(text.toLowerCase())) {
							suggestions.add(new ListItem(trackImage, track.getName()));
						}
					}
				}
			}
			
			return suggestions;
		}
		
	}
}
